<% if false # the license inside this if block assertains to this file -%>
# Copyright 2017 Google Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
<% end -%>
<%= compile 'templates/license.erb' -%>

<%= lines(autogen_notice :ruby) -%>

# Add our google/ lib
$LOAD_PATH.unshift ::File.expand_path('../libraries', ::File.dirname(__FILE__))

<%
  require 'google/string_utils'

  inside_indent = 8

  requires = generate_requires(object.all_user_properties)
  requires << 'chef/resource'
  requires << 'google/hash_utils'
  requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'get')
  unless object.readonly
    requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'delete')
    requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'post')
    requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'put')
  end
-%>
<%= lines(emit_requires(requires)) -%>

module Google
  module <%= @api.prefix.upcase %>
    # A provider to manage <%= @api.name -%> resources.
<%= lines(indent(
      emit_rubocop(binding, :class,
                   ['Google', @api.prefix.upcase, object.name].join('::'),
                   :disabled),
      4)) -%>
    class <%= object.name -%> < Chef::Resource
      resource_name :<%= object.out_name %>

<% object.all_user_properties.each do |prop| -%>
<%
    prop_name = "property :#{property_out_name(prop)}"
    prop_name = "property :#{label_name(object)}" if prop.out_name == 'name'
    prop_attrs = [
      ('name_property: true' if prop.out_name == 'name'),
      'desired_state: true'
    ].compact
-%>
<% if prop_decl(prop) == "Array" -%>
<%=
  lines(format([
    ["# #{property_out_name(prop)} is Array of #{prop.property_type}"],
    [
     "# #{property_out_name(prop)} is Array of",
     "# #{prop.property_type}"
    ]
  ], 6))
-%>
<% end -%>
<%=
  lines(format([
                 # All on one line
                 [
                   ["#{prop_name}, #{prop_decl(prop)}",
                    "coerce: ::#{prop.property_type}.coerce",
                    prop_attrs].flatten.join(', ')
                 ],
                 # Prop name on first line, all others on second line
                 [
                   "#{prop_name}",
                   ["#{indent(prop_decl(prop), 9)}",
                    "coerce: ::#{prop.property_type}.coerce",
                    prop_attrs].flatten.join(', ')
                 ],
                 # Prop name on first line, prop types on second line
                 # All others on third line
                 [
                   "#{prop_name},",
                   "#{indent(prop_decl(prop), 9)},",
                   [indent("coerce: ::#{prop.property_type}.coerce", 9),
                    prop_attrs].flatten.join(', ')
                 ],
                 # Prop name on first line, prop types on second line
                 # Coercion one-liner on third line
                 # Rest of attributes on fourth line
                 [
                   "#{prop_name},",
                   "#{indent(prop_decl(prop), 9)},",
                   indent("coerce: ::#{prop.property_type}.coerce,", 9),
                   "#{indent(prop_attrs.join(', '), 9)}"
                 ],
                 # Prop name on first line, prop types on second line
                 # Coercion two-liner on third + fourth lines
                 # Rest of attributes on fifth line
                 [
                   "#{prop_name},",
                   "#{indent(prop_decl(prop), 9)},",
                   indent("coerce: \\", 9),
                   indent("::#{prop.property_type}.coerce,", 11),
                   "#{indent(prop_attrs.join(', '), 9)}"
                 ],
               ], 6))
-%>
<% end -%>

      property :credential, String, desired_state: false, required: true
      property :project, String, desired_state: false, required: true

<% if object.save_api_results? -%>
      # TODO(alexstephen): Check w/ Chef how to not expose this property yet
      # allow the resource to store the @fetched API results for exports usage.
      property :__fetched, Hash, desired_state: false, required: false

<% end # object.save_api_results? -%>
      action :create do
<% if object.self_link_query.nil? -%>
<% if object.kind? -%>
        fetch = fetch_resource(@new_resource, self_link(@new_resource),
                               '<%= object.kind -%>')
<% else # object.kind? -%>
        fetch = fetch_resource(@new_resource, self_link(@new_resource))
<% end # object.kind? -%>
<% else # object.self_link_query.nil? -%>
        fetch = fetch_wrapped_resource(@new_resource, '<%= object.kind -%>',
                                       '<%= object.self_link_query.kind -%>',
                                       '<%= object.self_link_query.items -%>')
<% end # object.self_link_query.nil? -%>
        if fetch.nil?
<%=
  lines(format([
    ["converge_by \"Creating #{object.out_name}[\#{new_resource.name}]\" do"],
    [
      "converge_by ['Creating #{object.out_name}',",
      indent('"[#{new_resource.name}]"].join do', 13)
    ]
  ], 10, 0))
-%>
            # TODO(nelsonjr): Show a list of variables to create
            # TODO(nelsonjr): Determine how to print green like update converge
            puts # making a newline until we find a better way TODO: find!
            compute_changes.each { |log| puts "    - #{log.strip}\n" }
<% if object&.handlers&.create.nil? -%>
<%
     if object.create_verb.nil? || object.create_verb == :POST
       body_new = 'collection(@new_resource)'
       request_new = "::Google::#{product_ns}::Network::Post"
     elsif object.create_verb == :PUT
       body_new = 'self_link(@new_resource)'
       request_new = "::Google::#{product_ns}::Network::Put"
     end
-%>
            create_req = <%= request_new -%>.new(
              <%= body_new -%>, fetch_auth(@new_resource),
              'application/json', resource_to_request
            )
<%   unless object.async -%>
<%
  kind_param = object.kind? ? ", '#{object.kind}'" : ''
-%>
<%     if object.save_api_results? -%>
            @new_resource.__fetched =
              return_if_object create_req.send<%= kind_param %>
<%     else # object.save_api_results? -%>
            return_if_object create_req.send<%= kind_param %>
<%     end # object.save_api_results? -%>
<%   else # object.async -%>
<%     if object.save_api_results? -%>
            @new_resource.__fetched =
              wait_for_operation create_req.send, @new_resource
<%     else # object.save_api_results? -%>
            wait_for_operation create_req.send, @new_resource
<%     end # object.save_api_results? -%>
<%   end # object.async -%>
<% else # object&.handlers&.create.nil? -%>
<%= lines(indent(object.handlers.create, 12)) -%>
<% end -%>
          end
        else
          @current_resource = @new_resource.clone
<%
  fetch_code = object.properties.reject(&:input).map do |prop|
    name = property_out_name(prop)
    name = label_name(object) if prop.out_name == 'name'
    api_name = prop.name
    type = "::#{prop.property_type}"
    field_name = prop.field || prop.name
    if api_name.include?('.')
      fetch_tree = api_name.split('.').join(' ')
      assignment = format(
        [
          [
            "@current_resource.#{name} = #{type}.api_parse(",
            indent("::Google::HashUtils.navigate(fetch, %w[#{fetch_tree}])", 2),
            ')'
          ],
          [
            "@current_resource.#{name} =",
            indent([
              "#{type}.api_parse(",
              indent("::Google::HashUtils.navigate(fetch, %w[#{fetch_tree}])",
                     2),
              ')'
            ], 2)
          ],
          [
            "@current_resource.#{name} =",
            indent([
              "#{type}.api_parse(",
              indent([
                '::Google::HashUtils.navigate(',
                indent("fetch, %w[#{fetch_tree}]", 2),
                ')'
              ], 2),
              ')'
            ], 2)
          ]
        ], 0, 12
      )
    else
      assignment = format(
        [
          [
           "@current_resource.#{name} =",
           "#{type}.api_parse(fetch['#{field_name}'])"
          ].join(' '),
          [
            "@current_resource.#{name} =",
            indent("#{type}.api_parse(fetch['#{field_name}'])", 2)
          ],
          [
            "@current_resource.#{name} =",
            indent([
              "#{type}.api_parse(",
              indent("fetch['#{field_name}']", 2),
              ')'
            ], 2)
          ]
        ], 0, 12
      )
    end
  end
-%>
<%= lines(indent(fetch_code, 10)) -%>
<% if object.save_api_results? -%>
          @new_resource.__fetched = fetch
<% end # object.save_api_results? -%>

          update
        end
      end

      action :delete do
<% if object.self_link_query.nil? -%>
<% if object.kind? -%>
        fetch = fetch_resource(@new_resource, self_link(@new_resource),
                               '<%= object.kind -%>')
<% else # object.kind? -%>
        fetch = fetch_resource(@new_resource, self_link(@new_resource))
<% end # object.kind? -%>
<% else # object.self_link_query.nil? -%>
<% obj_kind = object.kind? ? "'#{object.kind}'," : '' -%>
<% if object.self_link_query.kind.nil? -%>
        fetch = fetch_wrapped_resource(@new_resource, <%= obj_kind %>
                                       '<%= object.self_link_query.items -%>')
<% else #object.self_link_query.kind.nil? -%>
        fetch = fetch_wrapped_resource(@new_resource, <%= obj_kind %>
                                       '<%= object.self_link_query.kind -%>',
                                       '<%= object.self_link_query.items -%>')
<% end #object.self_link_query.kind.nil? -%>
<% end # object.self_link_query.nil? -%>
        unless fetch.nil?
<%=
  lines(format([
    ["converge_by \"Deleting #{object.out_name}[\#{new_resource.name}]\" do"],
    [
      "converge_by ['Deleting #{object.out_name}',",
      indent('"[#{new_resource.name}]"].join do', 13)
    ]
  ], 10, 0))
-%>
<% if object&.handlers&.delete.nil? -%>
            delete_req = ::Google::<%= product_ns -%>::Network::Delete.new(
              self_link(@new_resource), fetch_auth(@new_resource)
            )
<%   unless object.async -%>
<%     obj_kind = object.kind? ? ", '#{object.kind}'" : '' -%>
            return_if_object delete_req.send<%= obj_kind %>
<%   else # object.async -%>
            wait_for_operation delete_req.send, @new_resource
<%   end # object.async -%>
<% else # object&.handlers&.delete.nil? -%>
<%= lines(indent(object.handlers.delete, 12)) -%>
<% end # object&.handlers&.delete.nil? -%>
          end
        end
      end

      # TODO(nelsonjr): Add actions :manage and :modify

<% unless object.exports.nil? -%>
<%
  exp_list = [
    '{',
    indent_list(object.exported_properties.map do |p|
      exp_name = p.out_name
      exp_name = label_name(object) if p.name == 'name'
      if p.is_a?(Api::Type::FetchedExternal)
        "#{p.out_name}: __fetched['#{p.field_name}']"
      else
        "#{p.out_name}: #{exp_name}"
      end
    end, 2),
    '}'
  ]
-%>
<%= lines(indent(emit_method('exports', [], exp_list, file_relative), 6), 1) -%>
<% end -%>
      private

      action_class do
<%
  prop_code = []
  prop_code << "kind: '#{object.kind}'" if object.kind?
  prop_code.concat(object.properties.reject { |p| p.output }
                                    .map do |prop|
    override_name = property_out_name(prop)
    override_name = label_name(object) if override_name == 'name'
    format([
      ["#{prop.field_name}: new_resource.#{override_name}"],
      ["#{prop.field_name}:",
       indent("new_resource.#{override_name}", 2)],
    ], 0, 12)
  end)

  prop_code.concat((object.parameters || [])
           .select { |p| p.input }
           .map do |prop|
             override_name = property_out_name(prop)
             override_name = label_name(object) if override_name == 'name'
             format([
               ["#{prop.field_name}: new_resource.#{override_name}"],
               ["#{prop.field_name}:",
                indent("new_resource.#{override_name}", 2)],
             ], 0, 12)
           end
  )

  r2r_code = []
  r2r_code << 'request = {'
  r2r_code << indent_list(prop_code, 2)
  r2r_code << '}.reject { |_, v| v.nil? }'

  unless object&.handlers&.resource_to_request_patch.nil?
    r2r_code << object.handlers.resource_to_request_patch
    r2r_code << ''
  end # object&.handlers&.resource_to_request_patch.nil?

  if object.encoder?
    r2r_code << '# Format request to conform with API endpoint'
    r2r_code << "request = #{object.transport.encoder}(request)"
  end

  r2r_code << 'request.to_json'
  -%>
<%=
  lines(indent(emit_method('resource_to_request', [], r2r_code, file_relative),
               8), 1)
-%>
<%
  unless false?(object.unwrap_resource)
    unless object.self_link_query.nil?
-%>
<%
  urf_code = [
    '{',
    indent_list(
      Hash[object.identity.map { |i| [i, "resource.#{property_out_name(i)}"] }]
        .map { |k, v| "#{k.out_name}: #{v}" }, 2
    ),
    '}'
  ]
-%>
        def unwrap_resource_filter(resource)
          self.class.unwrap_resource_filter(resource)
        end

<%= lines(indent(emit_method('self.unwrap_resource_filter', %w[resource],
                             urf_code, file_relative), 8), 1) -%>
<%   end # unless object.self_link_query.nil? -%>
<% end # visible:unwrap_resource -%>
        def update
          converge_if_changed do |_vars|
            # TODO(nelsonjr): Determine how to print indented like upd converge
            # TODO(nelsonjr): Check w/ Chef... can we print this in red?
            puts # making a newline until we find a better way TODO: find!
            compute_changes.each { |log| puts "    - #{log.strip}\n" }
<% if object&.handlers&.update.nil? -%>
<%   put_new = "::Google::#{product_ns}::Network::Put.new" -%>
<%= lines(indent("update_req =", 12)) -%>
<%=
  lines(indent_list([
    "#{put_new}(self_link(@new_resource)"].concat(
    indent([
      'fetch_auth(@new_resource)',
      "'application/json'",
      'resource_to_request)'
    ], put_new.length + 1).split("\n")
  ), 14))
-%>
<%   if object.async -%>
            wait_for_operation update_req.send, @new_resource
<%   else # object.async -%>
            return_if_object update_req.send, '<%= object.kind -%>'
<%   end # object.async -%>
<% else # object&.handlers&.update.nil? -%>
<%= lines(indent(object.handlers.update, 12)) -%>
<% end # object&.handlers&.update.nil? -%>
          end
        end
<% unless object.all_resourcerefs.empty? -%>

        def self.fetch_export(resource, type, id, property)
          return if id.nil?
          resource.resources("#{type}[#{id}]").exports[property]
        end
<% end # object.exports.nil? -%>

<%
  all_properties = object.all_user_properties
  has_project_property = \
     !object.all_user_properties.select { |o| o.name == 'project' }.empty?
  project_arg = has_project_property ? [] : ['project: resource.project']
  has_name = !object.all_user_properties.select { |o| o.name == 'name' }.empty?
  name_prop = "name: resource.name"
  name_prop = "name: resource.#{label_name(object)}" if has_name
  r2h_code = [
    '{',
    indent_list(project_arg.concat(
      [
        name_prop,
        ("kind: '#{object.kind}'" if object.kind?)
      ].compact
    ).concat(all_properties.reject { |p| p.name == 'name' }.map do |prop|
      format([
        ["#{prop.out_name}: resource.#{property_out_name(prop)}"],
        [
          "#{prop.out_name}:",
          indent("resource.#{property_out_name(prop)}", 2)
        ]
      ], 0, 12)
    end), 2),
    '}.reject { |_, v| v.nil? }'
  ]
-%>
<%= lines(indent(emit_method('self.resource_to_hash', %w[resource], r2h_code,
                             file_relative), 8)) -%>

        # Copied from Chef > Provider > #converge_if_changed
        def compute_changes
          properties = @new_resource.class.state_properties.map(&:name)
          properties = properties.map(&:to_sym)
          if current_resource
            compute_changes_for_existing_resource properties
          else
            compute_changes_for_new_resource properties
          end
        end

        # Collect the list of modified properties
        def compute_changes_for_existing_resource(properties)
          specified_properties = properties.select do |property|
            @new_resource.property_is_set?(property)
          end
          modified = specified_properties.reject do |p|
            @new_resource.send(p) == current_resource.send(p)
          end

          generate_pretty_green_text(modified)
        end

        def generate_pretty_green_text(modified)
          property_size = modified.map(&:size).max
          modified.map! do |p|
            properties_str = if @new_resource.sensitive
                               '(suppressed sensitive property)'
                             else
                               [
                                 @new_resource.send(p).inspect,
                                 "(was #{current_resource.send(p).inspect})"
                               ].join(' ')
                             end
            "  set #{p.to_s.ljust(property_size)} to #{properties_str}"
          end
        end

        # Write down any properties we are setting.
        def compute_changes_for_new_resource(properties)
          property_size = properties.map(&:size).max
          properties.map do |property|
            default = ' (default value)' \
              unless @new_resource.property_is_set?(property)
            next if @new_resource.send(property).nil?
            properties_str = if @new_resource.sensitive
                               '(suppressed sensitive property)'
                             else
                               @new_resource.send(property).inspect
                             end
            ["  set #{property.to_s.ljust(property_size)}",
             "to #{properties_str}#{default}"].join(' ')
          end.compact
        end

<% unless object.self_link_query.nil? -%>
<%
  r2q_code = [
    '{',
    indent_list(
      Hash[object.identity.map { |i| [i, "resource.#{i.out_name}"] }]
        .map { |k, v| "#{k.out_name}: #{v}" }, 2
    ),
    '}'
  ]
-%>
        def resource_to_query_predicate(resource)
          self.class.resource_to_query_predicate(resource)
        end

<%= lines(indent(emit_method('self.resource_to_query_predicate', %w[resource],
                             r2q_code, file_relative), inside_indent), 1) -%>
<% end # unless object.self_link_query.nil? -%>
        def fetch_auth(resource)
          self.class.fetch_auth(resource)
        end

        def self.fetch_auth(resource)
          resource.resources("gauth_credential[#{resource.credential}]")
                  .authorization
        end

<% if object.kind? -%>
        def fetch_resource(resource, self_link, kind)
          self.class.fetch_resource(resource, self_link, kind)
        end
<% else -%>
        def fetch_resource(resource, self_link)
          self.class.fetch_resource(resource, self_link)
        end
<% end -%>

        def debug(message)
          Chef::Log.debug(message)
        end

<% if object&.handlers&.collection.nil? -%>
<%= lines(indent(emit_link('collection', collection_url(object), true), 8)) %>
<% else # object&.handlers&.collection.nil? -%>
<%=
  lines(indent(emit_link('collection', object.handlers.collection, true), 8))
 %>
<% end # object&.handlers&.collection.nil? -%>
<% if object&.handlers&.self_link.nil? -%>
<%= lines(indent(emit_link('self_link', self_link_url(object), true), 8), 1) -%>
<% else # object&.handlers&.self_link.nil? -%>
<%=
  lines(indent(emit_link('self_link', object.handlers.self_link, true), 8), 1)
-%>
<% end # object&.handlers&.self_link.nil? -%>
<% if object&.handlers&.return_if_object.nil? -%>
<%= lines(indent(compile('templates/return_if_object.erb'), 8)) %>
<% else # object&.handlers&.return_if_object.nil? -%>
<%= lines(indent(object.handlers.return_if_object, 8), 1) -%>
<% end # object&.handlers&.return_if_object.nil? -%>
<%= lines(indent(compile('templates/expand_variables.erb'), 8)) %>
<%=
  if object.async
    lines(indent(compile('templates/async.erb'), inside_indent), 1)
  end
-%>
<%= lines(indent(compile('templates/provider_helpers.erb'), 8), 1) -%>
<%= lines(indent(compile('templates/transport.erb'), 8)) -%>
      end
    end
<%= lines(indent(
      emit_rubocop(binding, :class,
                   ['Google', @api.prefix.upcase, object.name].join('::'),
                   :enabled),
      4)) -%>
  end
end
